package com.gitee.fanx.retty.spring;

import com.gitee.fanx.retty.spring.common.ResponseStatus;
import com.gitee.fanx.retty.web.BaseExceptionHandler;
import com.gitee.fanx.retty.web.ExceptionResult;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class AnnotationExceptionHandler extends BaseExceptionHandler {
	private final ExceptionResult ERROR_RESULT = new ExceptionResult(HttpResponseStatus.INTERNAL_SERVER_ERROR, null);

	private Map<Class<? extends Exception>, ExceptionHandlerInvoker> invokerMap = new HashMap<>();

	public AnnotationExceptionHandler(Collection<Object> beans) {
		Assert.notEmpty(beans, "@RestControllerAdvice bean not found");
		beans.forEach(bean -> {
			ReflectionUtils.doWithMethods(bean.getClass(), (method) -> {
				ExceptionHandler exceptionHandlerAnnotation = method.getAnnotation(ExceptionHandler.class);
				if (exceptionHandlerAnnotation == null || exceptionHandlerAnnotation.value().length == 0) {
					return;
				}
				ResponseStatus responseStatusAnnotation = method.getAnnotation(ResponseStatus.class);
				HttpResponseStatus status = HttpResponseStatus.valueOf(responseStatusAnnotation == null ? 500 : responseStatusAnnotation.value());
				Assert.notNull(status, "HttpResponseStatus [" + responseStatusAnnotation.value() + "] not found");
				ExceptionHandlerInvoker invoker = new ExceptionHandlerInvoker(status, bean, method);
				for (Class<? extends Exception> clazz : exceptionHandlerAnnotation.value()) {
					invokerMap.put(clazz, invoker);
				}
			});
		});
	}

	@Override
	public ExceptionResult handle(Throwable e) {
		ExceptionHandlerInvoker invoker = invokerMap.get(e.getClass());
		if (invoker == null) {
			return ERROR_RESULT;
		}
		try {
			return invoker.invoke(e);
		} catch (Exception ex) {
			return ERROR_RESULT;
		}
	}

	public static class ExceptionHandlerInvoker {
		private HttpResponseStatus responseStatus;
		private Object bean;
		private Method method;
		public ExceptionHandlerInvoker(HttpResponseStatus responseStatus, Object bean, Method method) {
			this.responseStatus = responseStatus;
			this.bean = bean;
			this.method = method;
		}
		public ExceptionResult invoke(Throwable e) throws InvocationTargetException, IllegalAccessException {
			return new ExceptionResult(responseStatus, method.invoke(bean, e));
		}
	}
}
